package com.cloud.common.mock;

import com.cloud.common.bean.SessionInfo;
import com.cloud.common.context.AppContext;
import com.cloud.common.feign.FeignAttribute;
import com.cloud.common.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import java.lang.annotation.Annotation;
import java.lang.reflect.Parameter;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Aspect
@Component
@ConditionalOnMissingClass("com.cloud.gateway.bean.GatewayInfo")
public class HttpMockInterceptor {
    private static final String SEPERATOR = ".";
    private static final String PREFIX = "mock";
    private static final String UTF_8 = "utf-8";

    @Autowired
    private Environment environment;

    @Autowired
    private RestTemplate restTemplate;

    @Around("@within(HttpMock)")
    public Object doMock(ProceedingJoinPoint point) throws Throwable {
        if (canMock(point, null)) {
            return invokeMock(point, null);
        }
        return point.proceed();
    }

    public boolean canMock(ProceedingJoinPoint point, FeignAttribute feignAttribute) {
        if (AppContext.isProdEnv() || StringUtils.isEmpty(getMockUrl(point, feignAttribute))) {
            return false;
        }
        return true;
    }

    public Object invokeMock(ProceedingJoinPoint point, FeignAttribute feignAttribute) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        HttpParams httpParams = getHttpParams(point);
        String requestURL = getMockUrl(point, feignAttribute) + httpParams.getQueryString();

        LinkedMultiValueMap headerMap = new LinkedMultiValueMap();
        headerMap.set(SessionInfo.DEBUG, Boolean.TRUE.toString());
        HttpEntity<Object> httpEntity = new HttpEntity<>(httpParams.body, headerMap);
        ResponseEntity<String> response = restTemplate.exchange(requestURL, getHttpMethod(signature), httpEntity, String.class, httpParams.pathParams);
        Object result = JsonUtils.parseObject(response.getBody(), signature.getMethod().getGenericReturnType());
        log.info("requestURL >>> {}, result >>> {}, params >>> {}", requestURL, response.getBody(), JsonUtils.toJSONString(httpParams));
        return result;
    }

    private String getMockUrl(ProceedingJoinPoint point, FeignAttribute feignAttribute) {
        if (feignAttribute != null) {
            // 优先根据服务名找环境变量设置的请求地址
            String serviceName = feignAttribute.getServiceName();
            String requestURL = System.getenv(serviceName);
            if (StringUtils.isEmpty(requestURL)) {
                // 根据服务名找配置文件配置的请求地址
                requestURL = environment.getProperty(serviceName);
            }
            if (StringUtils.isNotEmpty(requestURL)) {
                requestURL = requestURL + feignAttribute.getRequestUri();
                return requestURL;
            }
        }
        // 根据接口类名和方法名找配置文件配置的请求地址
        MethodSignature signature = (MethodSignature) point.getSignature();
        String key = PREFIX + SEPERATOR + signature.getDeclaringType().getSimpleName()
                + SEPERATOR + signature.getName();
        return environment.getProperty(key);
    }

    private HttpMethod getHttpMethod(MethodSignature signature) {
        for (Annotation annotation : signature.getMethod().getAnnotations()) {
            if (annotation instanceof GetMapping) {
                return HttpMethod.GET;
            }
            if (annotation instanceof PostMapping) {
                return HttpMethod.POST;
            }
            if (annotation instanceof PutMapping) {
                return HttpMethod.PUT;
            }
            if (annotation instanceof DeleteMapping) {
                return HttpMethod.DELETE;
            }
        }
        return null;
    }

    private HttpParams getHttpParams(ProceedingJoinPoint point) {
        HttpParams httpParams = new HttpParams();
        MethodSignature signature = (MethodSignature) point.getSignature();
        String[] parameterNames = signature.getParameterNames();
        if (ArrayUtils.isEmpty(parameterNames)) {
            return httpParams;
        }
        Map<String, Parameter> parameterMap = Arrays.stream(signature.getMethod().getParameters())
                .collect(Collectors.toMap(Parameter::getName, Function.identity()));
        Object[] parameterValues = point.getArgs();
        for (int i = 0; i < parameterNames.length; i++) {
            Object value = parameterValues[i];
            Parameter parameter = parameterMap.get(parameterNames[i]);
            // path注解
            String name = Optional.ofNullable(parameter.getAnnotation(PathVariable.class))
                    .map(PathVariable::value).orElse("");
            if (StringUtils.isNotEmpty(name)) {
                httpParams.pathParams.put(name, value);
                continue;
            }
            // request注解
            name = Optional.ofNullable(parameter.getAnnotation(RequestParam.class))
                    .map(RequestParam::value).orElse("");
            if (StringUtils.isNotEmpty(name)) {
                httpParams.queryParams.put(name, value);
                continue;
            }
            // 基础类型和string则默认为query参数
            if (ClassUtils.isPrimitiveWrapper(value.getClass()) || value instanceof String) {
                httpParams.queryParams.put(parameterNames[i], value);
                continue;
            }
            httpParams.body = value;
        }
        return httpParams;
    }

    class HttpParams {
        Map<String, Object> pathParams = new HashMap<>();
        Map<String, Object> queryParams = new HashMap<>();
        Object body;

        String getQueryString() {
            if (queryParams.isEmpty()) {
                return "";
            }
            List<String> list = new ArrayList<>();
            queryParams.forEach((name, value) -> {
                try {
                    list.add(name + "=" + URLEncoder.encode(value.toString(), UTF_8));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            });
            return "?" + StringUtils.join(list, "&");
        }
    }

}
